﻿/*!
 *      CameraPool.cs         
 *      Date: 31/01/2012
 *      
 *      ----------------------------------------------------------------------
 *      A static wrapper for managing multiple cameras by name or index string.
 *      ----------------------------------------------------------------------
 * 
 *      This file contains two classes, the CameraPool itself, which holds CameraInstance's
 * 
 *      Usage of CameraPool:
 *          To clean all cameras (usually at the end up update cycle):
 *              CameraPool.CLEAN_ALL();
 *          To add a camera:
 *              CameraPool.AddCamera("MainCam", CameraInstance.CREATE_XXXX());
 *          To get a specified camera from the pool:
 *              CameraPool.Get("MainCam");
 * 
 *      Usage of CameraInstance:
 *          To create an camera instance:
 *              CameraInstace.CREATE_XXXX()
 *          To modify a camera instance:
 *              MyInstance.Roll += 5;
 *          To get matrices:
 *              MyInstance.View
 *              MyInstance.Projection
 *          To clean:       (Note: When working with multiple cameras the CameraPool has a function to clean all, but you can just use one if you really want.)
 *              MyInstance.CLEAN()
 *      
 *      Author:
 *      Adrian Seeley adrian@gatosomina.com
 *      
 *      License:
 *      FULLY OPEN
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNAREUSABLES
{
    public static class CameraPool
    {
        // The actual camera pool.
        private static Dictionary<String, CameraInstance> m_Cameras = new Dictionary<String, CameraInstance>();

        /// <summary>
        /// Adds a camera to the pool with a specified name for accessing.
        /// </summary>
        /// <param name="Camera_Name">The reference name of the camera for accessing staticically, handy for debugging multiple cameras.</param>
        /// <param name="Instance">The actual camera instance to be added.</param>
        public static void           AddCamera(String Camera_Name, CameraInstance Instance)
        {
            m_Cameras.Add(Camera_Name, Instance);
        }

        /// <summary>
        /// Returns the requested camera.
        /// </summary>
        /// <param name="Camera_Name">The name of the camera to be returned from the pool.</param>
        /// <returns>The requested camera.</returns>
        public static CameraInstance GetCamera(String Camera_Name)
        {
            return m_Cameras[Camera_Name];
        }

        /// <summary>
        /// Returns the requested camera.
        /// This is the fastest method for accessing cameras, but exercise caution with large pools and mis-indexed cameras!
        /// </summary>
        /// <param name="Camera_Name">The name of the camera to be returned from the pool.</param>
        /// <returns>The requested camera.</returns>
        public static CameraInstance GetCamera(int Camera_Index)
        {
            return m_Cameras.ElementAt(Camera_Index).Value;
        }

        /// <summary>
        /// Cleans all cameras.
        /// To be called at the end of the update cycle, after all cameras have been given updated values.
        /// </summary>
        public static void           CLEAN_ALL()
        {
            for (int iter = 0; iter < m_Cameras.Count; iter++)
            {
                m_Cameras.ElementAt(iter).Value.CLEAN();
            }
        }
    }

    /// ///////////////////////////////////
    /// /// ///////////////////////////////////
    /// ///////////////////////////////////

    public class CameraInstance
    {
        private bool       m_isOrthographic;                   // Is the camera orthographic, of persepctive?
        public  bool         isOrthographic
        {
            get { return m_isOrthographic;  }
            set { m_isOrthographic = value; }
        }                   // Get/Set is camera orthographic.
        public  bool         isPerspective
        {
            get { return !m_isOrthographic; }
        }                    // Get is camera perspective? (inherent)

        private Matrix     m_View;                             // The view matrix.
        private bool       m_View_isDirty = true;              // Does the view matrix need to be recalculated?
        public  Matrix       View
        {
            get 
            {
                if (m_View_isDirty)
                {
                    Vector3 campos = Vector3.Transform(Offset, Matrix.CreateFromQuaternion(Orientation));
                    campos += Position;

                    Vector3 camup = Vector3.Up;
                    camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(Orientation));

                    m_View = Matrix.CreateLookAt(campos, Position, camup);

                    //m_View *= Scale;
                    //m_View *= Rotation;
                    //m_View *= Translation;
                    
                    m_View_isDirty = false;
                    return m_View;  
                }
                else
                {
                    return m_View;
                }
            }
        }                             // Get/Set view matrix.

        private float      m_Near_Plane;                       // The near view plane.
        private float        Near_Plane
        {
            get { return m_Near_Plane; }
            set { m_Near_Plane = value; }
        }                       // Get/Set the near view plane.
        private float      m_Far_Plane;                        // The far view plane
        private float        Far_Plane
        {
            get { return m_Far_Plane; }
            set { m_Far_Plane = value; }
        }                        // Get/Set the far view plane.
        private float      m_Field_Of_View;                    // The field of view.
        private float        Field_Of_View
        {
            get { return m_Field_Of_View; }
            set { m_Field_Of_View = value; }
        }                    // Get/Set the field of view.
        private float      m_Aspect_Ratio;                     // The aspect ratio of the projection.
        private float        Aspect_Ratio
        {
            get { return m_Aspect_Ratio; }
            set { m_Aspect_Ratio = value; }
        }                     // Get/Set the aspect ratio of the projection.
        private Matrix     m_Projection;                       // The projection matrix.
        private bool       m_Projection_isDirty = true;        // Does the projection matrix need to be recalculated?
        public  Matrix       Projection
        {
            get 
            {
                if (m_Projection_isDirty)
                {
                    m_Projection = Matrix.CreatePerspectiveFieldOfView(Field_Of_View, Aspect_Ratio, Near_Plane, Far_Plane);
                    m_Projection_isDirty = false;
                    return m_Projection;
                }
                else
                {
                    return m_Projection;
                }
            }
        }                       // Get/Set projection matrix

        private Quaternion m_Orientation;                      // The orientation of the camera
        public  Quaternion   Orientation
        {
            get
            {
                return m_Orientation;
            }
            set
            {
                m_Orientation = value;
                m_View_isDirty = true;
            }
        }                      // Get/Set the orientation of the camera
        private Matrix       Rotation
        {
            get
            {
                return Matrix.CreateFromQuaternion(Orientation);
            }
        }                         // Get orientation matrix (or rotation matrix)

        private Vector3    m_Up;                               // The up vector of the camera.
        private Vector3      Up
        {
            get { return m_Up; }
            set 
            { 
                m_Up = value;
                m_View_isDirty = true;
            }
        }                               // Get/Set the up vector of the camera.

        private Vector3    m_Offset;                           // The camera body offset, THIS IS NOT THE TRANSLATION
        public  Vector3      Offset
        {
            get { return m_Offset;  }
            set 
            { 
                m_Offset = value;
                m_View_isDirty = true;
            }
        }                           // Get/Set camera body offset.
        private Vector3    m_Position;                         // The camera body translation (or position).
        public Vector3      Position
        {
            get { return m_Position; }
            set
            {
                m_Position = value;
                m_View_isDirty = true;
            }
        }                         // Get/Set the camear body translation (or position).
        private Matrix       Translation                         // Get the translation matrix (Calculated from Position).
        {
            get
            {
                return Matrix.CreateTranslation(Position);
            }
        }                      // Get the position translation matrix.

        private float      m_Zoom;                             // The cameras zoom in percentage //this might need tweeking
        public  float        Zoom
        {
            get { return m_Zoom; }
            set { m_Zoom = value; }
        }                             // Get/Set zoom
        private Matrix       Scale
        {
            get { return Matrix.CreateScale(Zoom); }
        }                            // Get the scale matrix. (Calculated from the Zoom)

        ///////////////////////////////////////////////////////////


        /// <summary>
        /// Cleans all dirty values. (Usually at the end of the update cycle)
        /// Is called by camera pool during CLEAN_ALL
        /// </summary>
        public void                  CLEAN()
        {
            /// <summary>
            /// Clean works by polling the accessors of variables that may become 'dirty' or need updating.
            /// If the value is already clean, the values reference is simply returned (virutal costless),
            /// however if the value is dirty, it is cleaned.  You might save a tick is you didn't clean the
            /// projection matrix every time.  But that's one tick, and would require splitting into:
            /// CLEAN_VIEWS()
            /// CLEAN_PROJS()
            /// </summary>
            
            // Create a local matrix to pass cleaned values too.
            Matrix null_mat; 
            null_mat = View;            // Cleans the view matrix which inherently cleans the rotation quaternions.
            null_mat = Projection;      // Cleans the projection matrix.
        }

        /// <summary>
        /// Move the camera forward, relative to it's current rotation.
        /// </summary>
        /// <param name="Distance">How far forward to move.</param>
        public void Move_Forward(float Distance)
        {
            Vector3 Delta = Vector3.Transform(new Vector3(0, 0, -1), Orientation);
            Offset += Delta * Distance;
        }

        /// <summary>
        /// Move the camera bakward, relative to it's current rotation.
        /// </summary>
        /// <param name="Distance">How far backward to move.</param>
        public void Move_Backward(float Distance)
        {
            Vector3 Delta = Vector3.Transform(new Vector3(0, 0, 1), Orientation);
            Offset += Delta * Distance;
        }

        /// <summary>
        /// Move the camera left, relative to it's current rotation.
        /// </summary>
        /// <param name="Distance">How far left to move.</param>
        public void Move_Left(float Distance)
        {
            Vector3 Delta = Vector3.Transform(new Vector3(0, 0, 0), Orientation);
            Offset += Delta * Distance;
        }

        /// <summary>
        /// Move the camera right, relative to it's current rotation.
        /// </summary>
        /// <param name="Distance">How far right to move.</param>
        public void Move_Right(float Distance)
        {
            Vector3 Delta = Vector3.Transform(new Vector3(0, 0, 0), Orientation);
            Offset += Delta * Distance;
        }


        /// <summary>
        /// Create a basic perspective camera, 45° FoV, NearP = 0.1, FarP = 1000, Zoom = 1.
        /// </summary>
        /// <param name="Width">The width of the render target.</param>
        /// <param name="Height">The height of the render target.</param>
        /// <param name="Position">The body position of the camera.</param>
        /// <returns>A standard camera with the specified properties.</returns>
        public static CameraInstance CREATE_STANDARD_PERSPECTIVE_CAMERA(
            float   Width,
            float   Height,
            Vector3 Offset)
        {
            return CREATE_PERSPECTIVE_CAMERA(MathHelper.PiOver2, Width, Height, 0.1f, 1000, Offset, 1.0f);
        }

        /// <summary>
        /// Create a perspective camera factory style.
        /// </summary>
        /// <param name="Field_Of_View">The field of view.</param>
        /// <param name="Width">The width of the render target.</param>
        /// <param name="Height">The height of the render target.</param>
        /// <param name="Near_Plane">The distance to the near plane.</param>
        /// <param name="Far_Plane">The distance to the far plane.</param>
        /// <param name="Position">The body position of the camera.</param>
        /// <param name="Zoom">The zoom of the camera.</param>
        /// <returns>A camera instance with the specified properties.</returns>
        public static CameraInstance CREATE_PERSPECTIVE_CAMERA(
            float   Field_Of_View, 
            float   Width, 
            float   Height, 
            float   Near_Plane, 
            float   Far_Plane, 
            Vector3 Offset, 
            float   Zoom)
        {
            // Create an instance.
            CameraInstance cam = new CameraInstance();

            // Set orthographic flag to false (inherent: perspective flag to true).
            cam.isOrthographic = false;

            // Calculate the aspect ratio
            cam.Aspect_Ratio = Width / Height;

            // Associate field of view
            cam.Field_Of_View = Field_Of_View;

            // Associate planes
            cam.Near_Plane = Near_Plane;
            cam.Far_Plane = Far_Plane;

            // Associate position
            cam.Offset  = Offset;

            // Associate zoom
            cam.Zoom      = Zoom;

            // Return instance.
            return cam;                                           
        }
    }
}
